package org.misty.seckill.redis;

import org.misty.seckill.util.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

@Service
public class RedisService {
  @Autowired
  private JedisPool jedisPool;

  public String get(String key) {
    return run(KeyPrefix.EMPTY, key, Jedis::get);
  }

  public <T> T get(String key, Class<T> cls) {
    String value = run(KeyPrefix.EMPTY, key, Jedis::get);
    return JSONUtil.parse(value, cls);
  }

  public String get(KeyPrefix prefix, String key) {
    return run(prefix, key, Jedis::get);
  }

  public <T> T get(KeyPrefix prefix, String key, Class<T> cls) {
    String value = run(prefix, key, Jedis::get);
    return JSONUtil.parse(value, cls);
  }

  public <T> boolean set(String key, T value) {
    var json = JSONUtil.toJsonString(value);
    var ret = run(KeyPrefix.EMPTY, key, (jedis, realKey) -> jedis.set(realKey, json));
    return "OK".equals(ret);
  }

  public <T> boolean set(KeyPrefix prefix, String key, T value) {
    var expire = prefix.getExpiryInSec();
    var json = JSONUtil.toJsonString(value);
    String ret = run(prefix, key, (jedis, realKey) -> {
      if (expire <= 0) {
        return jedis.set(realKey, json);
      } else {
        return jedis.setex(realKey, expire, json);
      }
    });
    return "OK".equals(ret);
  }

  public <T> boolean set(KeyPrefix prefix, String key, T value, SetParams params) {
    var json = JSONUtil.toJsonString(value);
    String ret = run(prefix, key, (jedis, realKey) -> jedis.set(realKey, json, params));
    return "OK".equals(ret);
  }

  public Long hset(KeyPrefix prefix, String key, Map<String, String> params) {
    return run(prefix, key, (jedis, realKey) -> jedis.hset(realKey, params));
  }

  public Long sadd(KeyPrefix prefix, String key, Object... values) {
    var jsons = Arrays.stream(values).map(JSONUtil::toJsonString).collect(Collectors.toList());
    var ary = new String[jsons.size()];
    return run(prefix, key, (jedis, realKey) -> jedis.sadd(realKey, jsons.toArray(ary)));
  }

  public Boolean exists(KeyPrefix prefix, String key) {
    return run(prefix, key, Jedis::exists);
  }

  public Long incr(KeyPrefix prefix, String key) {
    return run(prefix, key, Jedis::incr);
  }

  public Long decr(KeyPrefix prefix, String key) {
    return run(prefix, key, Jedis::decr);
  }

  public boolean del(KeyPrefix prefix, String key) {
    var ret = run(prefix, key, Jedis::del);
    return ret > 0;
  }

  public Long expire(KeyPrefix prefix, String key, int expiryInSec) {
    return run(prefix, key, (jedis, realKey) -> jedis.expire(realKey, expiryInSec));
  }

  public String scriptLoad(String script) {
    try (var jedis = jedisPool.getResource()) {
      return jedis.scriptLoad(script);
    }
  }

  public Object evalsha(String sha1) {
    try (var jedis = jedisPool.getResource()) {
      return jedis.evalsha(sha1);
    }
  }

  public Object evalsha(String sha1, List<String> keys, List<Object> args) {
    List<String> jsons = args.stream().map(JSONUtil::toJsonString).collect(Collectors.toList());

    /*var start = System.currentTimeMillis();
    var jedis = jedisPool.getResource();
    var end = System.currentTimeMillis();
    var res = jedis.evalsha(sha1, keys, jsons);
    jedis.close();
    var end2 = System.currentTimeMillis();
    System.out.println((end2 - start) + " " + (end - start));
    return res;*/

    try (var jedis = jedisPool.getResource()) {
      return jedis.evalsha(sha1, keys, jsons);
    }
  }

  public String flushAll() {
    try (var jedis = jedisPool.getResource()) {
      return jedis.flushAll();
    }
  }

  public String realKey(KeyPrefix prefix, String key) {
    String realKey;
    if (prefix == null || prefix == KeyPrefix.EMPTY) {
      realKey = key;
    } else {
      realKey = prefix.getPrefix() + key;
    }
    return realKey;
  }

  // ========================================================

  public <R> R run(KeyPrefix prefix, String key, BiFunction<Jedis, String, R> fn) {
    var realKey = realKey(prefix, key);
    try (var jedis = jedisPool.getResource()) {
      return fn.apply(jedis, realKey);
    }
  }


}
